package com.xuan.gateway.filter;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.AntPathMatcher;
import com.alibaba.fastjson2.JSON;
import com.xuan.common.core.constants.CacheConstants;
import com.xuan.common.core.constants.HttpConstants;
import com.xuan.common.core.constants.JwtConstants;
import com.xuan.common.core.domain.LoginUser;
import com.xuan.common.core.domain.R;
import com.xuan.common.core.enums.ResultCode;
import com.xuan.common.core.enums.UserIdentity;
import com.xuan.common.redis.service.RedisService;
import com.xuan.common.core.utils.JwtUtils;
import com.xuan.gateway.properties.IgnoreWhiteProperties;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Objects;

@Component
@Slf4j
public class AuthFilter implements GlobalFilter, Ordered {

    @Value("${jwt.secret}")
    private String secret;

    @Autowired
    private IgnoreWhiteProperties whiteProperties;

    @Autowired
    private RedisService redisService;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 查看调用接口是否处于白名单内，是则直接通过
        ServerHttpRequest httpRequest = exchange.getRequest();
        String url = httpRequest.getURI().getPath();
        if (matches(url, whiteProperties.getWhite())) {
            return chain.filter(exchange);
        }

        // 2. 查看用户是否携带 token，验证 token 的合法性
        // 从 header 中获取 token
        String token = getToken(httpRequest);
        if (StringUtils.isBlank(token)) {
            return unauthorizedResponse(exchange, "令牌为空,请重新登录");
        }
        Claims claims;
        try {
            claims = JwtUtils.parseToken(token, secret);
            // 非法令牌
            if (claims == null) {
                // SpringCloud gateway 基于webflux
                return unauthorizedResponse(exchange, "令牌已过期或验证不正确,请重新登录");
            }
        } catch (Exception e) {
            return unauthorizedResponse(exchange, "令牌已过期或验证不正确,请重新登录");
        }

        // 3. token 解析成功，验证用户登录状态是否过期
        // 从令牌中获取存放在 redis 中的 key
        String tokenKey = getTokenKey(claims);
        boolean isLogin = redisService.hasKey(tokenKey);
        if (!isLogin) {
            return unauthorizedResponse(exchange, "登录状态已过期");
        }

        // 管理员只能请求 B 端接口，普通用户只能请求 C 端接口
        LoginUser user = redisService.getCacheObject(tokenKey, LoginUser.class);
        if (url.contains(HttpConstants.SYSTEM_URL_PREFIX) && Objects.equals(user.getIdentity(), UserIdentity.ORDINARY.getValue())) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }
        if (url.contains(HttpConstants.FRIENDS_URL_PREFIX) && Objects.equals(user.getIdentity(), UserIdentity.ADMIN.getValue())) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }
        return chain.filter(exchange);
    }


    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
        // 先打印异常信息，再处理相应数据
        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
        return webFluxResponseWriter(exchange.getResponse(), msg,
                ResultCode.FAILED_UNAUTHORIZED.getCode());
    }

    /**
     *   拼装webflux模型响应
     */
    private Mono<Void> webFluxResponseWriter(ServerHttpResponse response,
                                             String msg, int code) {
        // 设置响应的 Http状态码和响应体数据类型
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE,
                MediaType.APPLICATION_JSON_VALUE);
        R<?> result = R.fail(msg, code);
        DataBuffer dataBuffer =
                response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
        return response.writeWith(Mono.just(dataBuffer));
    }

    private String getToken(ServerHttpRequest httpRequest) {
        String token = httpRequest.getHeaders().getFirst(HttpConstants.AUTHENTICATION);
        if (token != null && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replace(HttpConstants.PREFIX, "");
        }
        return token;
    }

    private String getTokenKey(Claims claims) {
        return CacheConstants.LOGIN_TOKEN_KEY + (String) claims.get(JwtConstants.LOGIN_USER_KEY);
    }

    private boolean matches(String url, List<String> patternList) {
        if (StringUtils.isBlank(url) || CollUtil.isEmpty(patternList)) {
            return false;
        }
        // 逐个匹配白名单路径规则
        for (String pattern : patternList) {
            if (isMatch(pattern, url)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断url是否与规则匹配
     * 匹配规则中：
     * ? 表⽰单个字符;
     * * 表⽰⼀层路径内的任意字符串，不可跨层级;
     * ** 表⽰任意层路径;
     *
     * @param pattern 匹配规则
     * @param url     url
     * @return 是否匹配
     */
    private boolean isMatch(String pattern, String url) {
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        return antPathMatcher.match(pattern, url);
    }

    @Override
    public int getOrder() {
        return -200;
    }
}
